package top.jiangqiang.crawler.core.http;

import lombok.RequiredArgsConstructor;
import okhttp3.*;
import okhttp3.logging.HttpLoggingInterceptor;
import org.dromara.hutool.core.text.StrUtil;
import org.jetbrains.annotations.NotNull;
import top.jiangqiang.crawler.core.config.GlobalCrawlerConfig;
import top.jiangqiang.crawler.core.constants.RequestMethod;
import top.jiangqiang.crawler.core.entities.*;
import top.jiangqiang.crawler.core.exception.CrawlerException;
import top.jiangqiang.crawler.core.http.constants.ContentType;
import top.jiangqiang.crawler.core.http.constants.HeaderName;
import top.jiangqiang.crawler.core.http.constants.HttpSchema;
import top.jiangqiang.crawler.core.http.entities.ProxyConfig;
import top.jiangqiang.crawler.core.http.entities.QueryParameters;
import top.jiangqiang.crawler.core.http.entities.RequestHeader;
import top.jiangqiang.crawler.core.http.entities.RequestLine;
import top.jiangqiang.crawler.core.http.entities.body.FormBody;
import top.jiangqiang.crawler.core.http.entities.body.MultipartBody;
import top.jiangqiang.crawler.core.http.entities.body.*;
import top.jiangqiang.crawler.core.mapstruct.BeanMapper;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

@RequiredArgsConstructor
public class OkHttpService implements HttpService {
    private final GlobalCrawlerConfig globalConfig;
    private final OkHttpClient httpClientWithoutProxy;

    public OkHttpService(GlobalCrawlerConfig globalConfig) {
        this.globalConfig = globalConfig;
        this.httpClientWithoutProxy = new OkHttpClient.Builder().addNetworkInterceptor(new HttpLoggingInterceptor(new HttpLogger()).setLevel(globalConfig.getLogLevel())).build();
    }

    @NotNull
    private static Request buildRequest(Crawler crawler) {
        return buildRequest(crawler.getUrl(), Objects.requireNonNullElseGet(crawler.getRequestLine(), RequestLine::new).method(), crawler.getQueryParameters(), crawler.getRequestHeader(), crawler.getRequestBody());
    }

    @NotNull
    public static Request buildRequest(String url, RequestMethod method, QueryParameters queryParameters, RequestHeader requestHeader, HttpBody<?> httpBody) {
        Request.Builder requestBuilder = new Request.Builder();
        if ((!url.startsWith(HttpSchema.HTTP.getValue())) && (!url.startsWith(HttpSchema.HTTPS.getValue()))) {
            url = HttpSchema.HTTP.getValue() + url;
        }
        HttpUrl.Builder urlBuilder = Objects.requireNonNull(HttpUrl.parse(url)).newBuilder();
        if (queryParameters != null) {
            queryParameters.forEach(urlBuilder::addQueryParameter);
        }
        requestBuilder.url(urlBuilder.build());
        if (requestHeader != null && !requestHeader.isEmpty()) {
            requestHeader.forEach((headerName, s) -> requestBuilder.addHeader(headerName.getValue(), s));
        }
        return switch (method) {
            case POST, PUT, PATCH, DELETE -> {
                HttpBody<?> crawlerRequestBody = Objects.requireNonNullElseGet(httpBody, FormBody::new);
                switch (crawlerRequestBody) {
                    case FormBody formBody -> {
                        okhttp3.FormBody.Builder formBodyBuilder = new okhttp3.FormBody.Builder();
                        Map<String, String> data = formBody.getData();
                        data.forEach(formBodyBuilder::add);
                        yield requestBuilder.method(method.name(), formBodyBuilder.build()).build();
                    }
                    case MultipartBody multipartBody -> {
                        okhttp3.MultipartBody.Builder multipartBodyBuilder = new okhttp3.MultipartBody.Builder()
                                .setType(okhttp3.MultipartBody.FORM);
                        Map<String, MultipartItemValueType<?>> data = multipartBody.getData();
                        data.forEach((name, multipartItemValueType) -> {
                            if (multipartItemValueType instanceof MultipartFileItem fileItem) {
                                RequestBody fileBody = RequestBody.create(fileItem.getValue(), MediaType.parse(ContentType.OCTET_STREAM.getValue()));
                                multipartBodyBuilder.addFormDataPart(name, fileItem.getValue().getName(), fileBody);
                            } else if (multipartItemValueType instanceof MultipartStringItem multipartStringItem) {
                                multipartBodyBuilder.addFormDataPart(name, multipartStringItem.getValue());
                            } else {
                                throw new CrawlerException("不支持的请求体类型");
                            }
                        });
                        yield requestBuilder.method(method.name(), multipartBodyBuilder.build()).build();
                    }
                    case RawStringBody rawStringBody -> {
                        MediaType mediaType = MediaType.parse(rawStringBody.getContentType(StandardCharsets.UTF_8));
                        RequestBody requestBody = RequestBody.create(rawStringBody.getBodyStr(), mediaType);
                        yield requestBuilder.method(method.name(), requestBody).build();
                    }
                    default -> throw new CrawlerException("不支持的请求体类型");
                }
            }
            //case null，如果这里不写的话，method为null会爆空指针。
            case null -> requestBuilder.method(RequestMethod.GET.name(), null).build();
            default -> requestBuilder.method(method.name(), null).build();
        };
    }

    public OkHttpClient getOkHttpClient(Crawler crawler) {
        Function<Crawler, ProxyConfig> proxyConfigFunction = globalConfig.getProxyConfigFunction();
        if (proxyConfigFunction == null) {
            return httpClientWithoutProxy;
        } else {
            ProxyConfig proxyConfig = proxyConfigFunction.apply(crawler);
            if (proxyConfig == null) {
                return httpClientWithoutProxy;
            } else {
                OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder()
                        .addNetworkInterceptor(new HttpLoggingInterceptor(new HttpLogger()).setLevel(globalConfig.getLogLevel()))
                        .proxy(new Proxy(proxyConfig.type(), new InetSocketAddress(proxyConfig.ip(), proxyConfig.port())));
                String username = proxyConfig.username();
                String password = proxyConfig.password();
                if (StrUtil.isNotBlank(username) && StrUtil.isNotBlank(password)) {
                    okHttpClientBuilder.proxyAuthenticator((route, response) -> {
                        //设置代理服务器账号密码
                        String credential = Credentials.basic(username, password);
                        return response.request().newBuilder().header(HeaderName.PROXY_AUTHORIZATION.getValue(), credential).build();
                    });
                }
                return okHttpClientBuilder.build();
            }
        }
    }

    @Override
    public Crawler request(Crawler crawler) {
        OkHttpClient okHttpClient = getOkHttpClient(crawler);
        Call call;
        try {
            call = okHttpClient.newCall(buildRequest(crawler));
        } catch (Exception e) {
            return BeanMapper.convert(crawler, ErrorCrawler.class);
        }
        try (Response response = call.execute()) {
            ResponseBody body = response.body();
            if (body != null) {
                MediaType mediaType = body.contentType();
                StreamCrawler streamCrawler = BeanMapper.convert(crawler, StreamCrawler.class);
                streamCrawler.setContentLength(body.contentLength());
                streamCrawler.setResponseCode(response.code());
                streamCrawler.setInputStream(body.byteStream());
                if (mediaType != null) {
                    streamCrawler.setContentType(mediaType.toString());
                    String type = mediaType.type();
                    if ("application".equals(type)) {
                        if ("json".equals(mediaType.subtype())) {
                            JsonCrawler jsonCrawler = BeanMapper.convert(streamCrawler, JsonCrawler.class);
                            jsonCrawler.setBodyBytes(body.bytes());
                            streamCrawler = jsonCrawler;
                        } else if ("xml".equals(mediaType.subtype())) {
                            XmlCrawler xmlCrawler = BeanMapper.convert(streamCrawler, XmlCrawler.class);
                            xmlCrawler.setBodyBytes(body.bytes());
                            streamCrawler = xmlCrawler;
                        } else if ("javascript".equals(mediaType.subtype())) {
                            TextCrawler textCrawler = BeanMapper.convert(streamCrawler, TextCrawler.class);
                            textCrawler.setBodyBytes(body.bytes());
                            streamCrawler = textCrawler;
                        }
                    } else if ("text".equals(type)) {
                        if ("html".equals(mediaType.subtype())) {
                            HtmlCrawler htmlCrawler = BeanMapper.convert(streamCrawler, HtmlCrawler.class);
                            htmlCrawler.setBodyBytes(body.bytes());
                            streamCrawler = htmlCrawler;
                        } else {
                            TextCrawler textCrawler = BeanMapper.convert(streamCrawler, TextCrawler.class);
                            textCrawler.setBodyBytes(body.bytes());
                            streamCrawler = textCrawler;
                        }
                    }
                }
                return streamCrawler;
            } else {
                return BeanMapper.convert(crawler, ErrorCrawler.class);
            }
        } catch (IOException e) {
            return BeanMapper.convert(crawler, ErrorCrawler.class);
        }
    }

}
